﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;

namespace LoRel
{
    public enum NullReport {
        AllPopulated,
        SomeNull,
        AllNull
    }

    public class Proxy {
        private readonly Type m_ExternalType;
        private readonly List<PropertyProxy> m_Proxies;

        public Proxy( Type external_type, IEnumerable< PropertyProxy > proxies ) {
            m_ExternalType = external_type;
            m_Proxies = new List< PropertyProxy >( proxies );
        }

        #region STATIC UTIL

        public static Proxy CreateProxy(Type proxy_type,
                                        IEnumerable<ColumnReference> columns) {

            List<PropertyProxy> properties = new List<PropertyProxy>();
            foreach( ColumnReference col in columns ){
                PropertyInfo property_info = proxy_type.GetProperty(col.Name);

                if( property_info == null) {
                    throw new InvalidProxyException( proxy_type, col );
                }

                MethodInfo get_method = property_info.GetGetMethod();
                if( get_method == null || get_method.ReturnType != col.Type ) {
                    throw new InvalidProxyException( proxy_type, col );
                }

                var getter = Emit.WrapGetter(property_info);
                properties.Add(new PropertyProxy(proxy_type, col, getter));
            }
            return new Proxy(proxy_type, properties);
        }

        #endregion

        public Type ExternalType { get { return m_ExternalType; } }
        public PropertyProxy this[int index] { get { return m_Proxies[ index ]; } }
        public int Count { get { return m_Proxies.Count; } }
        public IEnumerable<PropertyProxy> Items { get { return m_Proxies; } }

        public NullReport GetKey(Object external_instance, out Key key)
        {
            bool contains_nulls = false, is_all_null = true;
            
            IComparable[] objects = new IComparable[m_Proxies.Count];
            for (int i = 0; i < objects.Length; i++)
            {
                IComparable value = (IComparable)m_Proxies[i].Eval(external_instance);
                contains_nulls |= value == null;
                is_all_null &= value == null;
                objects[i] = value;
            }
            NullReport null_report = is_all_null
                                            ? NullReport.AllNull
                                            : contains_nulls
                                                  ? NullReport.SomeNull
                                                  : NullReport.AllPopulated;
            key = new Key( null_report, objects );

            return null_report;
        }
    }

    public class PropertyProxy {
        private readonly Type m_ProxyType;
        private readonly ColumnReference m_Column;
        private readonly Func<Object, Object> m_Func;
        
        public PropertyProxy( Type proxy_type, ColumnReference column, Func< object, object > func ) {
            m_ProxyType = proxy_type;
            m_Column = column;
            m_Func = func;
        }

        public Type ProxyType { get { return m_ProxyType; } }
        public ColumnReference Column { get { return m_Column; } }
        public Func< object, object > Func { get { return m_Func; } }
        
        // core functionality
        public Object Eval(Object proxy) { return m_Func(proxy); }
    }
}
